Skip to content

S03-10 JS-高级-ES6-基础语法

[TOC]

ES6

ECMA2015 规范

ECMA5 回顾

ECMA5回顾:在执行学习JavaScript代码执行过程中,我们学习了很多ECMA文档的术语:

  • 执行上下文栈:Execution Context Stack,用于执行上下文的栈结构;

  • 执行上下文:Execution Context,代码在执行之前会先创建对应的执行上下文;

  • 变量对象:Variable Object,上下文关联的VO对象,用于记录函数和变量声明;

  • 全局对象:Global Object,全局执行上下文关联的VO对象;

  • 激活对象:Activation Object,函数执行上下文关联的VO对象;

  • 作用域链:scope chain,作用域链,用于关联指向上下文的变量查找;

ECMA2015 概述

在新的ECMA代码执行描述中(ES5以及之上),对于代码的执行流程描述改成了另外的一些词汇:

  • 基本思路是相同的,只是对于一些词汇的描述发生了改变;

  • 执行上下文栈和执行上下文也是相同的;

ECMA2015ECMA 2015中代码执行流程描述:

  • 词法环境:Lexical Environment
    • 环境记录:Environment Record
      • 声明式环境记录:declarative Environment Record
      • 对象式环境记录:object Environment Record。就是window
    • 外部词法环境:outer Lexical Environment
  • 变量环境:Variable Environment

内存图@

image-20250730225908152

关联环境

在ES5之后,执行一个代码,通常会关联对应的词法环境;

那么执行上下文会关联哪些词法环境呢?

image-20230620140355292

变量环境 VariableEnvironment

变量环境(VariableEnvironment):用于处理 var/function 声明的标识符。

A var statement declares variables that are scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Environment Record is instantiated and are initialized to undefined when created. Within the scope of any VariableEnvironment a common BindingIdentifier may appear in more than one VariableDeclaration but those declarations collectively define only one variable. A variable defined by a VariableDeclaration with an Initializer is assigned the value of its Initializer's AssignmentExpression when the VariableDeclaration is executed, not when the variable is created.


var 声明 的变量作用域属于 正在运行的执行上下文的变量环境(VariableEnvironment)。这些变量在其所属的环境记录实例化时被创建,并在创建时初始化为 undefined。在同一变量环境的作用域内,相同的绑定标识符(变量名)可出现在多个变量声明中,这些声明共同定义同一个变量。当执行到带有初始化器的变量声明时,变量才会被赋予其初始化表达式的值(而非在变量创建时赋值)。

示例:变量提升

js
function foo() {
  console.log(msg) // 注意:此处可以访问msg,值为 undefined
  var msg = 'hello'
}
词法环境 Lexical Environment

词法环境(LexicalEnvironment):用于处理 let/const 声明的标识符。

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.


letconst 声明 的变量作用域属于 正在运行的执行上下文的词法环境(LexicalEnvironment)。这些变量在其所属的环境记录实例化时被创建,但在评估变量的词法绑定之前,无法以任何方式访问。当评估带初始化器的词法绑定时,变量才会被赋予初始化表达式的值(而非在变量创建时赋值)。若 let 声明无初始化器,则在评估词法绑定时赋值为 undefined

示例:暂时性死区

js
function foo() {
  console.log(msg) // 注意:不同于var,此处不能访问msg。该现象叫做暂时性死区
  let msg = 'hello'
}

词法环境作用

  • 词法环境是一种规范类型,用于在词法嵌套结构中定义关联的变量、函数等标识符
  • 词法环境常用于关联函数声明、代码块语句、try-catch语句,当它们的代码被执行时,词法环境被创建出来

词法环境组成:一个词法环境是由以下部分组成:

  • 环境记录(Environment Record)
  • 外部词法环境(Outer Lexical Environment)

image-20230620140346535

环境记录 Environment Record

环境记录组成:在这个规范中有两种主要的环境记录值:声明式环境记录和对象环境记录。

  • 声明式环境记录(Declarative Environment Records):声明性环境记录用于定义ECMAScript语言语法元素的效果,如:

    • function:函数声明。

    • var/let/const:变量声明。

    • try...catch:直接将标识符绑定与ECMAScript语言值关联起来的Catch子句。

  • 对象式环境记录(Object Environment Records):对象环境记录用于定义ECMAScript元素的效果,如

    • with:With语句,它将标识符绑定与某些对象的属性关联起来(不怎么使用)。

image-20230620140426996

ECMA2025 规范

关于JS的代码执行过程的内存描述,在ES2025规范中又发生了变化。

内存图@

image-20250801104928790

直接关联环境记录

ECMA执行上下文关联的词法环境和变量环境都是一个环境记录:

新变化

  1. 不再强调词法/变量环境,直接关联环境记录

    在最近的文档中已经不怎么强调词法环境的问题了,而是使用环境记录

    image-20250731113044801

    明确指出了 LexicalEnvironment和 VariableEnvironment 这两个组件“始终是环境记录(Environment Records)”

    这意味着执行上下文中的这两个组件直接指向了环境记录,而不再通过其他结构(如词法环境)间接管理标识符的绑定。

全局环境记录

全局环境记录(Global Environment Record)

全局执行上下文关联的是一个全局环境记录(Global Environment Record):

A Global Environment Record is used to represent the outer most scope that is shared by all of the ECMAScript Script elements that are processed in a common realm.


全局环境记录(Global Environment Record) 用于表示在同一个领域(realm)中处理的所有 ECMAScript 脚本元素所共享的最外层作用域。

解析:当多个脚本(<script> 标签、模块、动态加载的代码等)在同一个领域 (realm)(如一个浏览器标签页)中运行时,它们共享同一个全局环境记录

示例

html
<script> 
  const version = "1.0"; // 进入全局环境记录
</script>
<script>
  console.log(version); // "1.0"(通过共享的全局环境记录访问)
</script>

全局环境记录组成:全局环境记录包含一个声明环境记录和一个对象环境记录:

A Global Environment Record is logically a single record but it is specified as a composite encapsulating an Obiect Environment Record and a Declarative Environment Record.


全局环境记录(Global Environment Record) 在逻辑上是单一记录,但在规范中被定义为封装了对象环境记录(Object Environment Record)声明式环境记录(Declarative Environment Record) 的复合结构。

对比

组件类型绑定内容存储位置典型示例
对象环境记录var/函数声明全局对象属性var x = 10window.x
声明式环境记录let/const/class独立词法存储区const y = 20(不在全局对象)

示例

js
var globalFunc = () => {};   // 存入对象环境记录 → window.globalFunc
let lexicalVar = 42;         // 存入声明式环境记录

console.log(window.lexicalVar); // undefined(隔离存储)
console.log(delete globalFunc); // true(对象属性可删除)
console.log(delete lexicalVar); // false(词法绑定不可删除)

[[OuterEnv]]:是 ECMAScript 规范中环境记录(Environment Record) 的内部槽(internal slot),表示当前作用域的直接外层作用域。它是实现作用域链(Scope Chain)的核心机制。

核心特性

  1. 作用域链当前环境 → [[OuterEnv]] → [[OuterEnv]] → ... → null
  2. 词法作用域:指向定义时的外层环境(非执行时),实现闭包。
  3. 作用域链终点:全局环境的 [[OuterEnv]]null(作用域链终点)。
  4. 不可直接访问:内部实现机制,开发者无法通过代码直接操作。

image-20250731124730847

访问顺序

声明式环境记录和对象环境记录的访问顺序

在全局执行上下文,执行过程中有提到Object Environment Record 和 Declarative Environment Record,先查找谁?

查找一个 N 变量

The HasBinding concrete method of a Global Environment Record envRec takes argument N (a String) and returns either a normal completion containing a Boolean or a throw completion. It determines if the argument identifier is one of the identifiers bound by the record. It performs the following steps when called:

  1. Let DclRec be envRec.[[DeclarativeRecord]].
  2. If ! DclRec.HasBinding(N) is true, return true.
  3. Let ObjRec be envRec.[[ObjectRecord]].
  4. Return ? ObjRec.HasBinding(N).

全局环境记录 envRecHasBinding 具体方法 接收参数 N(字符串类型),并返回一个 包含布尔值的正常完成记录抛出完成记录。该方法用于判定参数标识符是否为当前环境记录所绑定的标识符之一,执行步骤如下:

  1. DclRecenvRec.[[DeclarativeRecord]]
  2. ! DclRec.HasBinding(N) 结果为 true,则返回 true
  3. ObjRecenvRec.[[ObjectRecord]]
  4. 返回 ? ObjRec.HasBinding(N)

解析:在查找一个变量名 N 时:

  1. 首先会在 [[DeclarativeRecord]] 中查找。
  2. [[DeclarativeRecord]] 中存在该绑定,则直接返回 true,即查找成功。
  3. 如果 [[DeclarativeRecord]] 中没有找到该绑定,才会继续在 [[ObjectRecord]] 中查找。

总结词法绑定(let/const)优先于对象绑定(var/function

函数环境记录【

函数环境记录(Function Environment Record):用于管理函数执行上下文中的标识符绑定。其核心作用包括:

  1. 存储函数局部绑定:包括形参、var/let/const声明的变量、函数声明、arguments对象。
  2. 维护this绑定:记录函数调用时的this值,并控制其是否可动态修改。
  3. 支持super方法调用:在类构造函数中解析super关键字。

函数环境记录的组成:函数环境记录只包含一个声明式环境记录

A Function Environment Record is a Declarative Environment Record that is used to represent the top-level scope of a function and, if the function is not an ArrowFunction, provides a this binding. If a function is not an ArrowFunction function and references super, its Function Environment Record also contains the state that is used to perform super method invocations from within the function.


函数环境记录(Function Environment Record) 是一种声明式环境记录(Declarative Environment Record),用于表示函数的顶层作用域。如果函数不是箭头函数,它还会提供 this 绑定;若函数(非箭头函数)引用了 super,则该环境记录还包含用于在函数内执行 super 方法调用的状态信息。


函数环境记录的工作流程

  1. 创建阶段(函数调用时)

    • 步骤1:基于函数的[[Scope]]属性(定义时的环境)创建新环境记录。

    • 步骤2:绑定形参(如未传参则初始化为undefined)和arguments对象。

    • 步骤3:扫描函数体,处理函数声明(提升)和变量声明(var初始化为undefinedlet/const处于TDZ)。

  2. 执行阶段

    • 变量访问:按“当前环境 → [[OuterEnv]]链”递归查找标识符,触发GetBindingValue

    • this解析:根据[[ThisBindingStatus]]决定是否允许动态修改this(如严格模式禁止修改)。

  3. 闭包的形成

    若内部函数引用外部函数的变量,外部函数的环境记录会被保留(即使外部函数已执行完毕),形成闭包。

    js
    function outer() {
      let x = 10;
      return function inner() { 
        console.log(x); // 闭包捕获outer的环境记录
      };
    }

声明式环境记录区分var/let/const

那么一个声明式环境记录是如何区分函数中存放的var变量、let/const这些的呢?

  • CreateMutableBinding:用于创建 var 变量的可变绑定。
  • CreateImmutableBinding:用于创建 let/const 变量的不可变绑定。
js
// 1. var 可以重复声明变量
var msg = "hello"
var msg = '你好' // OK

// 2. let/const 不可以重复声明变量
let age = 18
let age = 20 // 报错
const PI = 3.14
const PI = 3.1415927 // 报错

不可变绑定(ImmutableBinding):指一旦创建后其值不能被重新赋值的变量绑定,用于存储const声明的常量。特性如下:

  • 不可重新赋值:绑定创建后,任何重新赋值操作都会抛出 TypeError
  • 块级作用域:遵循词法作用域规则(不同于 var 的函数作用域)
  • TDZ(暂时性死区):声明前访问会抛出 ReferenceError

可变绑定(MutableBinding):【

let/const

基本使用

ES6引入了 letconst 关键字,极大地改进了 JS 的变量声明方式,解决了ES5中 var 的一些痛点,如作用域问题意外覆盖变量

letconst 在其他编程语言中都是有的,并不是新鲜的关键字。


let:与 var 类似,都是用于声明一个变量

const(constant,常量):用于声明一个常量

  1. 必须初始化: 声明时必须立即赋值。
  2. 不可重新赋值: 一旦赋值后,不能再给它赋一个新的值(尝试重新赋值会引发 TypeError)。

基本使用

  1. var的基本使用

    js
    // 1. 使用var声明变量
    var msg = 'Hello' 
    
    // 2. var声明的变量可以重新赋值
    msg = '你好' 
    console.log(msg)
  2. let的基本使用

    js
    // 1. 使用let声明变量
    let msg = '你好,世界'
    
    // 2. let声明的变量可以重新赋值
    msg = '你好,刘备'
    console.log(msg)
  3. const的基本使用

    js
    // 1. 使用const声明常量
    const PI = 3.14
    
    // 2. const声明的常量不能重新赋值
    PI = 3.1415 // ❌ Uncaught TypeError: Assignment to constant variable.
    console.log(PI)

核心特性

核心特性

  1. 块级作用域(Block Scope)
    • var 声明的变量只有全局作用域函数作用域
    • let/const/function/class声明的变量有块级作用域
  2. 暂时性死区 (TDZ,Temporal Dead Zone):从作用域开始到变量声明语句之间的代码区域。
  3. 禁止重复声明 (No Re-declaration)
  4. const 的特殊性:常量声明
块级作用域
基本语法

块级作用域(Block Scope):ES6核心特性,是 JS 中{} 花括号定义的代码范围(如 if/for/while 语句或独立代码块),使用 letconst 声明的变量在该范围内有效,外部无法访问。它解决了 var 只有函数作用域和全局作用域的问题。


语法

js
{ // 块级作用域开始
  let blockVar = "内部变量";
  console.log(blockVar); // ✅ "内部变量"
} // 块级作用域结束

console.log(blockVar); // ❌ ReferenceError: blockVar is not defined
特性

var 的作用域

  1. ES5中var只会形成两个作用域:全局作用域和函数作用域

    image-20250804163141621

  2. ES5中var没有块级作用域var在代码块中声明的变量,外面可以访问。

    image-20250804163252163

    image-20250804163435722


let/const/function/class的块级作用域

  1. ES6中新增了块级作用域,使用let/const/function/class声明的变量有块级作用域

    image-20250731175121889

  2. 函数特殊:函数有块级作用域,但外部后面依然可以访问

    原因:历史存在大量在外部访问代码块中function声明的函数,直接修改会造成大量库无法使用。所以引擎对函数的声明进行特殊的处理,允许像var一样在外界后面直接访问,但是不能在外界前面访问

    image-20250731180258880

内存图@

1 创建块级词法环境

代码块{}不会创建新的执行上下文,但是会创建新的词法环境。

image-20250801152255221

2 销毁块级词法环境

当执行完代码块之后,其所对应的词法环境会被销毁

image-20250801152816230

应用场景

应用:监听多个按钮的点击事件

image-20250801153226859

  1. 方案一:使用let/const实现(推荐)

    原理:利用了let/const的块级作用域的特性,此时点击事件函数的外层作用域是for循环的代码块。

    image-20250801155529136

  2. 方案二:使用var + 立即执行函数实现

    原理:利用立即执行函数形成一个闭包环境。

    image-20250801154316948

暂时性死区@

暂时性死区(TDZ,Temporal Dead Zone):是 ES6 中 letconst 特有的概念,指变量在作用域内已存在但尚未初始化的区域,此时访问变量会触发引用错误(ReferenceError)。


核心机制

  1. 进入作用域即创建绑定

    当程序执行进入一个作用域(如块级作用域 {})时,let/const 声明的变量会立即被创建,但不会初始化(没有赋默认值 undefined)。

  2. 声明语句前不可访问

    从作用域开始到变量声明语句之间的代码区域就是 TDZ。在此区域内访问变量会抛出错误:

    image-20250731164529500

    image-20250731164632139

  3. var 的对比

    js
    console.log(b);  // 输出: undefined(变量提升且初始化为 undefined)
    var b = 20;     // 实际执行顺序:1.声明并初始化 2.赋值
  4. TDZ取决于代码执行顺序,而非编写位置

    使用术语 “temporal” 是因为区域取决于执行顺序(时间),而不是编写代码的位置。

    image-20250731165101232

  5. TDZ形成后,不能在该区域访问标识符

    image-20250731165237252

    image-20250731165415343

禁止重复声明

同一个作用域内,使用 letconst 声明一个已经被 letconstvar 声明过的变量会引发 SyntaxError


var 允许重复声明

在同一个作用域内,可以多次使用 var 声明同一个变量(后面的会覆盖前面的),这容易导致错误。

js
var name = "Alice";
var name = "Bob"; // 允许,但容易出错
console.log(name); // 输出: Bob

let/const 不允许重复声明

  • 同一个作用域内,使用 letconst 声明一个已经被 letconstvar 声明过的变量会引发 SyntaxError

    js
    let age = 25;
    // let age = 30; // 报错: SyntaxError: Identifier 'age' has already been declared
    
    const PI = 3.14;
    // const PI = 3.14159; // 报错: SyntaxError: Identifier 'PI' has already been declared
  • 不同作用域没问题:

    js
    let age = 25;
    function test() {
      let age = 30; // 没问题,新的块级作用域
      console.log(age); // 输出: 30
    }
    test();
    console.log(age); // 输出: 25 (外部作用域的 age)
const 的特殊性

const 的特殊性

  1. 必须初始化: 声明时必须立即赋值。

  2. 不可重新赋值: 一旦赋值后,不能再给它赋一个新的值(尝试重新赋值会引发 TypeError)。

  3. const 与不可变性重要const 保证的是变量标识符绑定的不变性,而不是它指向的的不可变性:

    1. 原始类型

      如果值是原始类型如 number, string, boolean, 那么值本身也是不可变的。

      js
      // 原始值 - 完全不可变
      const MAX_SIZE = 100;
      // MAX_SIZE = 200; // 报错: TypeError: Assignment to constant variable.
    2. 对象和数组:

      如果 const 变量绑定的是一个对象或数组(引用类型),你不能将这个变量重新指向另一个对象或数组,但你可以修改该对象或数组内部的内容(属性、元素)。

      js
      // 引用值 (对象) - 绑定不可变,内容可变
      const person = {
        name: "Charlie"
      };
      person.name = "David"; // 允许,修改对象属性
      console.log(person); // 输出: { name: "David" }
      // person = { name: "Eve" }; // 报错: TypeError: Assignment to constant variable. (不能重新赋值)
      js
      // 引用值 (数组) - 绑定不可变,内容可变
      const numbers = [1, 2, 3];
      numbers.push(4); // 允许,修改数组内容
      console.log(numbers); // 输出: [1, 2, 3, 4]
      // numbers = [5, 6]; // 报错: TypeError: Assignment to constant variable. (不能重新赋值)

变量提升@

变量提升(Hoisting):是 JS 引擎在代码执行前的编译阶段将变量和函数声明提升到作用域顶部的行为。


提升规则

声明类型提升效果初始化值示例表现
var 变量✅ 声明提升undefinedconsole.log(a)undefined
函数声明✅ 整体提升(声明+函数体)函数定义foo() → 可正常执行
let / const⚠️ 提升但未初始化(TDZ)未初始化console.log(b)报错
函数表达式➖ 仅变量提升(按变量规则)undefinedbar()TypeError

对比varlet/const

  • var声明会提升,并初始化为 undefined

    js
    console.log(a); // 输出:undefined(而非报错)
    var a = 10;
  • let/const声明也会提升,但不被初始化,并会形成暂时性死区 TDZ

    js
    console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
    let b = 20;

let/const算是变量提升么?

  1. 变量创建时机

    let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.


    letconst 声明 的变量作用域属于 当前执行上下文的词法环境(LexicalEnvironment)这些变量在其所属的环境记录实例化时被创建,但在评估变量的词法绑定(变量赋值)之前,任何形式的访问都将被禁止。当执行带初始化器的词法绑定时,变量才会被赋予初始化表达式的值(而非在变量创建时赋值)。若 let 声明无初始化器,则在评估词法绑定时赋值为 undefined

    解释:在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。

  2. MDN结论:倾向于认为 let/const属于变量提升,但也接受不属于变量提升:

    1. 不属于提升理由暂时性死区严格禁止在声明之前使用变量,并且提升并不是一个普遍认同的术语。

    2. 属于提升理由:暂时性死区可以导致其作用域内的其他可观察变化,这表明存在某种形式的提升。

      js
      const x = 1;
      {
        // 如果没有提升行为, 此处是可以访问外部 x 值的,这表示 const 对该作用域存在“污染”
        console.log(x); // ReferenceError
        const x = 2;
      }

变量保存位置

变量是否保存在window上

  • 全局通过var声明一个变量,会在window上添加一个属性

    image-20250731170118153

  • 全局通过let/const不会在window上添加任何属性

    image-20250731170312423


let/const声明的变量保存位置

那么我们可能会想这个变量是保存在哪里呢?

A Global Environment Record is logically a single record but it is specified as a composite encapsulating an Obiect Environment Record and a Declarative Environment Record.


全局环境记录(Global Environment Record) 在逻辑上是单一记录,但在规范中被定义为封装了对象环境记录(Object Environment Record)声明式环境记录(Declarative Environment Record) 的复合结构。

结论

  • 对象环境记录

    一般就是windowvar/function/async function/Generator/async Generator/声明的变量保存在该记录上。

  • 声明式环境记录

    一般保存除了上述声明的变量,其中就包括let/const声明的变量。

image-20250731172517530

示例:let/const声明的变量保存位置

image-20250804174502563

对比 var/let/const

那么在开发中,我们到底应该选择使用哪一种方式来定义我们的变量呢?

对比var/let/const

特性letconstvar
作用域块级作用域块级作用域函数作用域 / 全局作用域
提升提升但未初始化 (TDZ)提升但未初始化 (TDZ)提升并初始化为 undefined
重复声明同一作用域内不允许同一作用域内不允许同一作用域内允许
重新赋值允许不允许允许
声明时初始化可选必须可选
典型用途循环计数器、需要重新赋值的变量配置值、模块导出、对象/数组引用旧代码 / 需要函数作用域时 (少用)

结论

  1. varvar 表现出的特性(变量提升,window全局对象,没有块级作用域)本质上是JS设计之初遗留的语言缺陷
  2. let/const推荐,优先推荐 constlet/const 就是为了解决 var 的缺陷才出现的。
    1. const:优先推荐,它可以保证数据的安全性:数据不会被随意篡改
    2. let:只有当我们明确知道一个变量后续会被重新赋值时,才会使用它

模板字符串

基本使用

ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)。

image-20250801161300119


模板字符串(Template Literals):是 ES6 引入的一种新型字符串语法,使用 反引号(`` 代替传统的单引号或双引号。它提供了比传统字符串更强大的功能,特别是在字符串插值多行字符串标签模板方面。


基本使用

  1. 使用反引号(``)代替传统的单引号或双引号
  2. 使用${expression}嵌入任意 JS 表达式或函数调用
js
const name = "Alice";
const age = 30;

const newWay = `Hello, ${name}! You are ${age} years old.`; // "Hello, Alice! You are 30 years old."

核心特性

核心特性

  1. 字符串插值

    在模板字符串中,可以使用 ${expression} 语法嵌入任意 JavaScript 表达式:

    js
    const name = "Alice";
    const age = 30;
    
    const newWay = `Hello, ${name}! You are ${age} years old.`; // "Hello, Alice! You are 30 years old."
  2. 表达式计算

    ${} 中可以包含任意有效的 JS 表达式执行函数

    js
    const a = 5;
    const b = 10;
    function foo() { return "foo function" }
    
    console.log(`The sum is ${a + b}`); // "The sum is 15"
    console.log(`Is a > b? ${a > b ? 'Yes' : 'No'}`); // "Is a > b? No"
    console.log(`My function is  ${foo()}`) // My function is foo function
  3. 多行字符串

    模板字符串天然支持多行内容,无需使用 \n 或字符串拼接

    js
    const newMultiline = `Line 1
    Line 2
    Line 3`;
    
    console.log(newMultiline);
    // 输出:
    // Line 1
    // Line 2
    // Line 3

标签模板字符串@

我们一起来看一个普通的 JS 函数:

image-20250801161943362


标签模板字符串(Tagged Template Literals):是 ES6 模版字符串的高级功能,允许你通过自定义函数解析模板字符串


核心特性

  1. 标签模板字符串:普通使用

    image-20250801162338366

    image-20250801162359736

  2. 标签模板字符串:嵌入变量

    如果我们使用标签模板字符串,并且在调用的时候插入其他的变量

    image-20250801163115042

    形参分析

    • 模板字符串被拆分了;

    • 第一个元素是数组,是被模块字符串拆分的字符串组合;

    • 后面的元素是一个个模块字符串嵌入的内容;


应用场景

  1. React的styled-components库

    image-20250801164242902

箭头函数

基本语法

箭头函数(Arrow Function):是 ES6 之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁。


语法

箭头函数如何编写呢?

js
const foo = (name, age) => {};
  • (): 函数的参数

  • {}: 函数的执行体


核心特性

  • 箭头函数不绑定thisargumentssuper参数

    image-20230620141055382

  • 箭头函数没有显式原型prototype

    所以不能作为构造函数,不能使用new来创建对象。

    image-20250802174419949

箭头函数缩写

优化一:如果只有一个参数可以省略()

js
const foo = name => {};

优化二:如果函数执行体中只有一行代码, 那么可以省略大括号

并且这行代码的返回值会作为整个函数的返回值。

js
const foo = (name) => console.log(name);
const foo = (name) => true;

优化三:如果函数执行体只返回一个对象, 那么需要给这个对象加上()

js
const foo = (name) => ({ name: "tom" });

箭头函数中的 this

ES5 中在异步函数中获取 this

实现思路

  1. 在异步函数setTimeout外部保存this到变量_this上。
  2. 在异步函数setTimeout内部通过访问变量_this来获取this。
js
// 1. ES5中在异步函数中获取this
const obj = {
  name: "obj",
  data: [],
  getData: function () {
    // 1. 在异步函数setTimeout外部保存this到变量_this上
    let _this = this
    setTimeout(function () { // 非箭头函数,会绑定this
      const res = ["tom", "jack", "jerry"]
      console.log(this) // => window
      // 2. 在异步函数setTimeout内部通过访问变量_this来获取this
      console.log(_this) // => obj
      _this.data.push(...res)
      console.log(_this.data)
    })
  }
}
obj.getData()

ES6 中在异步函数中获取 this

实现思路:使用箭头函数访问异步函数setTimeout外部的this。

原理箭头函数不绑定 this 对象,那么 this 引用就会从上层作用域中找到对应的 this。

js
// 2. 箭头函数中在异步函数中获取this
const obj2 = {
  name: 'obj2',
  data: [],
  getData: function () {
    setTimeout(() => { // 箭头函数,不绑定this
      const res = ['this', 'is', 'es6']
      // 箭头函数内部的this直接指向外部的this
      console.log(this) // => obj2
      this.data.push(...res)
      console.log(this.data)
    })
  }
}
obj2.getData()

思考: 如果 getData 也是一个箭头函数,那么 setTimeout 中的回调函数中的 this 指向谁呢?

答案:window,因为会继续向外查找,直到最顶层。

ES6 函数增强

默认参数值

基本使用

默认参数值:允许在函数定义时为参数设置默认值。当函数调用时未提供该参数或传递 undefined 时,将自动使用默认值。


ES5中的默认参数值

我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求:

  • 传入了参数,那么使用传入的参数;

  • 没有传入参数,那么使用一个默认值;

默认值ES5写法

  • 写法一:三元运算符

    问题:除了undefined/null之外,0/false/''等也会触发默认值。

    image-20250801171159614

  • 写法二:逻辑或运算符

    问题:除了undefined/null之外,0/false/''等也会触发默认值。

    image-20250801171317004

  • 写法三:默认值的严谨写法(只有undefine/null触发默认值)

    三元运算符写法

    image-20250801172131714

    空值合并运算符 写法

    image-20250801172506820


ES6+中的默认参数值

在ES6中,我们允许给函数一个默认值,只有undefined会触发默认值,null不会触发

js
// ES6 默认参数语法
function greet(name = 'Guest') {
  console.log(`Hello, ${name}!`);
}

greet();          // Hello, Guest!
greet('Alice');   // Hello, Alice!
greet(undefined); // Hello, Guest! (undefined 触发默认值)
greet(null);      // Hello, null! (null 不会触发默认值)
核心特性

ES6+默认参数值核心特性

  1. null不触发默认值:只有undefined或未传参数会触发默认值。

    js
    // ES6 默认参数语法
    function greet(name = 'Guest') {
      console.log(`Hello, ${name}!`);
    }
    
    greet();          // Hello, Guest!
    greet(undefined); // Hello, Guest! (undefined 触发默认值)
    
    greet(null);      // Hello, null! (null 不会触发默认值)
  2. 默认参数应放在参数列表末尾

    js
    // 正确
    function valid(a, b = 10) {}
    
    // 错误(语法可行但实际使用不便)
    function invalid(a = 5, b) {}
    invalid(undefined, 10); // 必须这样调用
  3. 影响length属性:默认参数会影响函数的 length 属性,默认参数以及之后所有参数都不会计算在length中。

    js
    function example(a, b = 1, c) {}
    console.log(example.length); // 1(只计算第一个没有默认值的参数)
  4. 不能重复声明默认参数:默认参数有自己的作用域

    js
    function scopeTest(a = 1) {
      let a = 2; // ❌ 错误:Identifier 'a' has already been declared
    }
  5. 默认值与解构赋值结合

    写法一:

    image-20250801175039061

    写法二:推荐

    js
    // 注意:解构语法中的默认值使用 = 号赋值
    function drawChart({ width = 100, height = 100, title = 'Chart' } = {}) {
      console.log(`Drawing ${title} (${width}x${height})`);
    } 
    
    drawChart(); // Drawing Chart (100x100)
    drawChart({ width: 200 }); // Drawing Chart (200x100)
  6. 剩余参数放最后:剩余参数必须放到最后一个位置,默认参数在剩余参数之前,否则会报错。

    image-20250801174239314

剩余参数

展开运算符

基本使用

展开运算符(Spread Operator):是 ES6 引入的重要特性,使用三个点 (...) 语法将可迭代对象(如数组、字符串、Map、Set 等)"展开"为独立元素,用于高效处理集合数据


基本使用

js
function sum(a, b, c) {
  console.log(a + b + c);
}

const nums = [1, 2, 3];
sum(...nums); // 6

核心特性

  1. 可展开对象
    1. 可以将 可迭代对象(如数组、字符串、Map、Set 等) 展开为独立元素。
    2. 对象类型虽然不是可迭代对象,但也可以展开(内部专门处理过)。
  2. 注意:展开运算符其实是一种浅拷贝
  3. 应用场景
    1. 数组构造:要求展开的数据必须是可迭代对象。
    2. 对象字面量构造:只能使用对象展开。
    3. 函数调用:要求展开的数据必须是可迭代对象。

对比 剩余参数

特性展开运算符剩余参数
语法位置函数调用、数组/对象字面量中函数参数定义中
主要功能展开集合为独立元素收集多个元素为单个数组
操作方向展开 (spread out)收集 (collect)
使用场景数组操作、对象操作、函数调用函数参数处理
可迭代要求✅ 必须可迭代❌ 无要求
可展开对象

可展开对象...可以将以下可迭代对象展开为独立元素:

  1. 数组展开
  2. 对象展开
  3. 字符串展开
  4. Set展开
  5. Map展开
数组

应用场景

  1. 合并数组

    js
    const arr1 = [1, 2];
    const arr2 = [3, 4];
    const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
  2. 复制数组(浅拷贝)

    js
    const original = [1, 2, 3];
    const copy = [...original]; // 浅拷贝:返回新数组,修改 copy 不影响 original
  3. 插入数组元素

    js
    const numbers = [3, 4];
    const newArray = [1, 2, ...numbers, 5, 6]; // [1, 2, 3, 4, 5, 6]
  4. 函数调用:同基本使用

    js
    function sum(a, b, c) {
      console.log(a + b + c);
    }
    
    const nums = [1, 2, 3];
    sum(...nums); // 6
对象

对象展开:是ES2018新特性,只能在构建对象字面量时使用

核心特性

  1. 对象展开无法用于函数调用

    在函数调用中使用展开运算符,要求展开的数据必须是可迭代对象(Array/String/arguments/Map/Set/...

    image-20250802180859813

  2. 对象展开只能用于构建对象字面量

    image-20250802181254131


应用场景

  1. 合并对象(浅合并)

    js
    const obj1 = { a: 1, b: 2 };
    const obj2 = { b: 3, c: 4 };
    const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }
  2. 复制对象(浅拷贝)

    js
    const original = { name: "Alice", age: 30 };
    const copy = { ...original }; // 返回新对象,修改 copy 不影响 original
  3. 添加/覆盖属性

    js
    const user = { name: "Bob" };
    const updatedUser = { ...user, age: 25, name: "Robert" }; 
    // { name: "Robert", age: 25 }
字符串

基本使用

  1. 数组构造

    js
    const str = "hello";
    const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
  2. 函数调用

    image-20250802175843248

Set

基本使用数组构造

js
const set = new Set([1, 2, 3]);
const array = [...set]; // [1, 2, 3]
Map

基本使用数组构造

js
const map = new Map([['a', 1], ['b', 2]]);
const entries = [...map]; // [['a', 1], ['b', 2]]

引用赋值/浅拷贝/深拷贝

进制表示

进制表示ES2015,在ES6中规范了二进制和八进制的写法:

  1. 二进制表示法 (0b 或 0B)

    js
    const binary = 0b1101; // 二进制 1101
    console.log(binary); // 13 (十进制)
    console.log(binary === 13); // true
  2. 八进制表示法 (0o 或 0O)

    js
    const octonary = 0o777; // 八进制 777
    console.log(octonary); // 511 (十进制)
    console.log(octonary === 511); // true
  3. 补充:十六进制表示法(0x 或 0X)

    js
    const hexadecimal = 0xff
    console.log(hexadecimal) // 255
    console.log(hexadecimal === 255); // true

数值分割符 _

数值分隔符ES2021,使用下划线 _ 提高大数值的可读性:

js
// 传统大数值
const billion = 1000000000;

// 使用数值分隔符
const readableBillion = 1_000_000_000;
const binaryWithSeparator = 0b1010_0001_1000_0101;
const hexWithSeparator = 0xA0_B0_C0_D0;

console.log(readableBillion === billion); // true

Symbol

Set

WeakSet

Map

WeakMap

ES7+

ES7

includes()

  • arr.includes()(valueToFind,fromIndex?),用来判断数组是否包含某个元素,返回布尔值。

ES8

Object.values()

  • Object.values()(obj)ES2017,用于获取对象自身可枚举属性值数组的静态方法。

Object.entries()

  • Object.entries()(obj)ES2017,用于获取对象自身可枚举属性的键值对数组的静态方法。

padStart()

  • str.padStart()(targetLength, padString?)ES2017,用于在字符串开头填充指定字符直到字符串达到目标长度。返回新字符串。

应用:时间格式化

image-20250806162118259


应用:隐藏身份证/银行卡敏感数据

image-20250806162649874

padEnd()

  • str.padEnd()(targetLength, padString?)ES2017,用于在字符串结尾填充指定字符直到字符串达到目标长度。返回新字符串。

尾后逗号

尾后逗号(Trailing Comma):是指在数组对象函数参数函数调用等结构中的最后一个元素后添加逗号的语法特性,JSON 不支持


语法

js
// 数组中的尾后逗号
const fruits = [
  'apple',
  'banana',
  'orange', // <- 这就是尾后逗号
];

// 对象中的尾后逗号
const person = {
  name: 'Alice',
  age: 30, // <- 尾后逗号
};

// 所有函数声明方式的参数中的尾后逗号
function greet(
  name,
  message = 'Hello', // <- 尾后逗号
) {
  console.log(`${message}, ${name}!`);
}

// 函数调用
console.log(
  'Result:',
  42, // ✅ 有效尾后逗号
);

// 解构赋值
const [first, second,] = [1, 2]; // ✅
const { name, age, } = person; // ✅

// 导入/导出
import {
  Component,
  useState, // ✅ 有效尾后逗号
} from 'react';

export {
  Button,
  Input, // ✅ 有效尾后逗号
};

尾后逗号的优点

  1. 版本控制友好:添加新元素时,git diff 更清晰:

    js
    const config = {
      apiUrl: 'https://api.example.com',
      timeout: 5000,
    +  retries: 3, // 只需添加这一行
    };
  2. 减少语法错误:避免忘记添加逗号

  3. 提高代码一致性:统一代码风格

  4. 简化代码重构:移动行时不需要调整逗号


注意事项

  1. JSON 不支持尾后逗号

    js
    // 无效 JSON
    {
      "name": "Alice",
      "age": 30, // ❌ SyntaxError: Unexpected token } in JSON
    }

Object.getOwnPropertyDescriptors()

  • Object.getOwnPropertyDescriptors()(obj)ES2017,用于获取对象所有自身属性的描述符的方法。是 getOwnPropertyDescriptor 的批量版,适用于需要全面分析或复制对象属性的场景(如深度克隆、元编程)。

async/await

ES9

Async Iterators【

对象展开运算

Promise.finally()

  • promise.finally()(onFinally)ES2018,用于指定一个无论 Promise 最终状态如何(fulfilled 或 rejected)都会执行的回调函数。

ES10

flat()

  • arr.flat()(depth?)ES2019纯函数,用于将嵌套的多维数组“拉平”为一维或多维数组,返回一个新数组。

flatMap()

  • arr.flatMap()(callback,thisArg?)ES2019,将映射(map)和扁平化(flat)操作合并为一步

案例:截取数组中的元素,并返回一维数组

  1. 方案1:2次for循环遍历(传统方式)

    image-20250806172835631

  2. 方案2:先map后flat操作

    image-20250806173039029

  3. 方案3:直接使用 flatMap(推荐

    image-20250806173147769

Object.fromEntries()

  • Object.fromEntries()(iterable)ES2019,用于将键值对列表转换为对象的静态方法。是 Object.entries() 的逆向操作。
  • new URLSearchParams()(init?),创建一个专门用于操作 URL 的查询参数的实例。

trimStart()

  • str.trimStart()()ES2019,用于移除字符串开头的空白字符(包括空格、制表符、换行符等)。返回新字符串,别名 trimLeft()

trimEnd()

  • str.trimEnd()()ES2019,用于移除字符串结尾的空白字符(包括空格、制表符、换行符等)。返回新字符串,别名 trimRight()

Symbol 描述

  • sym.descriptionstring|undefinedES2020只读,用于获取创建 Symbol 时传入的可选描述字符串

catch binding【

ES11

BigInt

BigInt:是 JS 在 ES2020 中引入的一种新的原始数据类型,用于表示任意精度的整数。它解决了 JS 中 Number 类型无法精确表示大于 2⁵³ - 1(即 9,007,199,254,740,991)的整数问题。


为什么需要 BigInt

JS 的 Number 类型基于 IEEE 754 双精度浮点数标准,这导致大于Number.MAX_SAFE_INTEGER(2⁵³ - 1) 的整数会出现精度丢失的问题:

js
// Number 类型的精度限制
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (精度丢失!)

创建 BigInt

js
// 字面量语法:添加后缀 n
const bigInt = 9007199254740993n;
console.log(bigInt); // 9007199254740993n

// BigInt() 构造函数(没有 new)
const fromString = BigInt("9007199254740993");
const fromNumber = BigInt(Number.MAX_SAFE_INTEGER);
const fromHex = BigInt("0x1fffffffffffff")

console.log(fromString); // 9007199254740993n
console.log(fromNumber); // 9007199254740991n
console.log(fromHex);    // 9007199254740991n

// 注意:不能直接转换小数
BigInt(3.14); // RangeError: The number 3.14 cannot be converted to a BigInt

注意事项

  1. 不能直接转换小数

    js
    BigInt(3.14); // RangeError: The number 3.14 cannot be converted to a BigInt
  2. typeof 运算

    js
    const bigInt = 9007199254740993n;
    console.log(typeof bigInt); // "bigint"

逻辑空 ??

可选链 ?.

可选链操作符(Optional Chaining)?.ES2020,用于安全地访问嵌套对象属性和调用方法,避免因中间属性不存在而导致的运行时错误。它极大地简化了深层嵌套对象的访问逻辑。


语法

js
// 传统方式
const street = user && user.address && user.address.street;

// 使用可选链
// 1. 访问对象属性
const street = user?.address?.street;

// 2. 访问数组元素
const name = users?.[0]?.name

// 3. 调用方法
api.fetchData?.()

// 4. 访问动态属性
const config = { theme: "dark" };
const property = "theme";
config?.[property]

工作原理

  • 如果 ?. 前面的值为 nullundefined,表达式立即返回 undefined
  • 否则,继续访问后面的属性或方法。

核心特性

  1. 不可用于赋值

    js
    const obj = {};
    obj?.property = "value"; // SyntaxError
  2. 可选链的短路行为

    当左侧为 null/undefined 时,右侧不会执行

    js
    // 当左侧为 null/undefined 时,右侧不会执行
    null?.property.x.y.z; // undefined (不会尝试访问 x)
  3. 谨慎使用括号

    js
    // ✅ 正确:访问可能的数组元素
    users?.[0]?.name;
    
    // ❌ 错误:语法错误
    (users?.[0])?.name;
  4. 性能考虑

    不必要的可选链会增加开销,仅在必要层级使用。

    js
    // 不必要的可选链会增加开销
    // ❌ 不推荐
    const name = user?.profile?.name;
    
    // ✅ 更好:仅在必要层级使用
    const name = user.profile?.name; // 假设 user 已存在

应用

  1. 结合逻辑空

    js
    const settings = {
      theme: null
    };
    
    const theme = settings?.theme ?? "light";
    console.log(theme); // "light" (因为 theme 是 null)
  2. 删除可选属性

    js
    const user = {
      profile: {
        name: "Alice"
      }
    };
    
    // 安全删除
    delete user?.profile?.name;
  3. 安全调用函数

    js
    // 安全调用可能不存在的回调
    const callback = undefined;
    callback?.("test"); // 不会执行
    
    // 带参数的方法调用
    const calculator = {
      add(a, b) { return a + b; }
    };
    calculator.add?.(2, 3); // 5

globalThis

  • globalThisobjectES2020全局属性,用于跨环境统一访问全局对象。它解决了 JS 在不同运行环境中全局对象名称不一致的问题。

for...in 标准化

在ES11之前,虽然很多浏览器支持for...in来遍历对象类型,但是并没有被ECMA标准化。

在ES11中,对其进行了 ECMA 标准化for...in 用于遍历对象的 key。

image-20250807143102841

import()

  • import()(modulePath),是 ES6 模块的动态导入语法,返回一个 Promise,允许在运行时异步加载模块。

import.meta

Promise.allSettled()

  • Promise.allsettled()(iterable)ES2020,用于等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的最终状态和结果。不会因某个 Promise 失败而提前终止。

ES12

逻辑赋值运算符

数值分隔符 _

replaceAll()

  • str.replaceAll()(searchValue, replaceValue)ES2021支持正则,用于全局替换字符串中所有匹配的子串。无需正则表达式即可实现全量替换。

FinalizationRegistry

WeakRef

ES13

at()

  • str.at()(index?)ES2022,用于获取指定索引位置的字符,支持正负索引访问。
  • arr.at()(index?)ES2022,用于通过索引访问数组元素,支持负数索引。

Object.hasOwn()

  • Object.hasOwn()(obj, prop)ES2022,用于检查对象是否直接拥有某个自身属性的静态方法。不检查原型链。
  • obj.hasOwnProperty()(prop),用于检查对象自身是否直接包含某个属性的实例方法。不检查原型链。

类中新成员

在ES13中,新增了定义class类中成员字段(field)的其他方式:

实例属性:public / private

  • 之前类中属性的写法

    在 constructor 中通过 this 设置

    image-20250807114353402

  • 最新类中属性的写法

    image-20250807114522884

    image-20250807115139811


静态属性:public / private

image-20250807114937929

image-20250807115108626


静态代码块:先执行静态代码块,再执行构造方法。

image-20250807115413217